"""
基于python的yield 和 yield from的特性 实现异步效果
关键词：
    事件驱动/时间循环
"""

import time
import socket
from functools import partial
from select import epoll, EPOLLIN

poll = epoll()
handlers = dict()

scheduled_list = []
# 创建一个timeout_list
timeout_list = []


class Future(object):

    def __init__(self):
        self.callbacks = []
        self.value = None

    def add_callback(self, callback):
        self.callbacks.append(callback)

    def set_done(self, value):
        self.value = value
        for callback in self.callbacks:
            callback()

    def get_result(self):
        return self.value


class Timeout(object):

    def __init__(self, timeout, callback):
        self.deadline = time.time() + timeout
        self.callback = callback

    def call(self):
        self.callback(None)


def sleep(sec):
    future = Future()
    timeout = Timeout(sec, future.set_done)
    timeout_list.append(timeout)
    return future


def fun():
    future = Future()

    print("step1")
    sock = socket.socket()
    fd = sock.fileno()
    poll.register(fd, EPOLLIN)

    handlers[fd] = future.set_done
    yield future
    print("step2")
    yield sleep(3)
    print("step3")
    yield


class Handle(object):
    def __init__(self, gen):
        self.gen = gen

    def call(self):
        yielded = next(self.gen)
        if isinstance(yielded, Future):
            yielded.add_callback(partial(scheduled_list.append, self))
        else:
            scheduled_list.append(self)


def loop(*coroutines):
    scheduled_list.extend(Handle(c) for c in coroutines)
    while True:

        default_timeout = 10000
        deadline = time.time()

        for timeout in timeout_list[:]:
            if timeout.deadline <= deadline:
                timeout.call()
                timeout_list.remove(timeout)

        while scheduled_list:
            handle = scheduled_list.pop(0)
            handle.call()

        for timeout in timeout_list:
            wait_time = timeout.deadline - deadline
            if wait_time <= 0:
                wait_time = 0
            default_timeout = min(default_timeout, wait_time)

        if not scheduled_list and not timeout_list and not handlers:
            break

        # 等待描述符可操作
        events = poll.poll(default_timeout)
        while events:
            fd, event = events.popitem()
            handlers[fd]()
            poll.unregister(fd)
            del handlers[fd]


if __name__ == "__main__":
    loop(fun(), fun(), fun())
